package com.ilikesoup.instant.enums.ser;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.BeanProperty;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonSerializer;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.ContextualSerializer;
import com.ilikesoup.instant.enums.basic.SelectEnum;

import java.io.IOException;
import java.util.Optional;

/**
 * 序列化<code>EnumCode</code>属性或<code>Getter</code>方法，将其值作为编码查询描述，并序列化到<code>json</code>中。
 *
 * 描述的名称可以通过 {@link EnumCodeSerialize#name()} 属性指定；如果不指定则默认在属性名称末尾加上 Name，
 * 如果属性名是以code>Code</code>结尾的，那么会将属性末尾的<code>Code</code>替换为<code>Name</code>
 *
 * 如果属性值为<code>null</code>，那么不会进入到该类处理，也就不会生成描述的字段，这可能会导致前端因为找不到对象属性而发生错误。
 * 为了避免错误，可以自定义<code>BeanSerializerModifier</code>来处理空值，并通过{@link NullEnumCodeSerializer}生成描述的字段。
 *
 * <pre> {@code
 * public class NullHandledBeanSerializerModifier extends BeanSerializerModifier {
 *
 *     @Override
 *     public List<BeanPropertyWriter> changeProperties(SerializationConfig config, BeanDescription beanDesc, List<BeanPropertyWriter> beanProperties) {
 *         for (BeanPropertyWriter writer : beanProperties) {
 *             // EnumCode属性
 *             EnumCodeSerialize enumCodeSerialize = writer.getAnnotation(EnumCodeSerialize.class);
 *             if(enumCodeSerialize != null) {
 *                 writer.assignNullSerializer(new EnumCodeSerializer.NullEnumCodeSerializer(writer.getName(), enumCodeSerialize.name()));
 *                 continue;
 *             }
 *
 * @Configuration
 * public class JacksonConfig {
 *
 *     @Bean
 *     public MappingJackson2HttpMessageConverter mappingJacksonHttpMessageConverter() {
 *         MappingJackson2HttpMessageConverter converter = new MappingJackson2HttpMessageConverter();
 *         ObjectMapper mapper = converter.getObjectMapper();
 *         mapper.setSerializerFactory(mapper.getSerializerFactory().withSerializerModifier(new NullHandledBeanSerializerModifier()));
 *         ...
 *         return converter;
 *     }
 *  </pre>
 *
 * @author Soup
 */

public class EnumCodeSerializer extends JsonSerializer<Object> implements ContextualSerializer {

    private final Descriptor descriptor;

    /**
     * 必须保留默认构造器
     */
    public EnumCodeSerializer() {
        this(null);
    }

    public EnumCodeSerializer(Descriptor descriptor) {
        this.descriptor = descriptor;
    }

    @Override
    public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
        gen.writeObject(value);
        Descriptor d = this.descriptor;
        if(d == null) {
            return;
        }
        gen.writeStringField(d.descName, Optional.ofNullable(
                SelectEnum.getByCodeGeneric(value, d.enumClass)).map(SelectEnum::getDesc).orElse(""));
    }

    @Override
    public JsonSerializer<?> createContextual(SerializerProvider prov, BeanProperty property) throws JsonMappingException {
        if (property == null) {
            return this;
        }
        EnumCodeSerialize enumCode;
        if ((enumCode = property.getAnnotation(EnumCodeSerialize.class)) != null && SelectEnum.class.isAssignableFrom(enumCode.value())) {
            return new EnumCodeSerializer(new Descriptor(property.getName(), getDescName(property.getName(), enumCode.name()), enumCode.value()));
        }
        return prov.findValueSerializer(property.getType(), property);
    }

    private static String getDescName(String codeName, String originDescName) {
        if(originDescName.length() > 0) {
            return originDescName;
        }
        if(codeName.endsWith("Code")) {
            return codeName.substring(0, codeName.length() - 4) + "Name";
        }
        return codeName + "Name";
    }


    private static class Descriptor {
        /**
         * 枚举编码名称
         */
        String codeName;
        /**
         * 枚举描述名称
         */
        String descName;
        /**
         * 枚举类
         */
        Class<? extends Enum<?>> enumClass;

        Descriptor(String codeName, String descName, Class<? extends Enum<?>> enumClass) {
            this.codeName = codeName;
            this.descName = descName;
            this.enumClass = enumClass;
        }
    }

    public static class NullEnumCodeSerializer extends JsonSerializer<Object> {

        private Descriptor descriptor;

        public NullEnumCodeSerializer(String codeName, String originDescName) {
            descriptor = new Descriptor(codeName, getDescName(codeName, originDescName), null);
        }

        @Override
        public void serialize(Object value, JsonGenerator gen, SerializerProvider serializers) throws IOException {
            if(value == null) {
                gen.writeNull();
                gen.writeStringField(descriptor.descName, "");
            } else {
                gen.writeObject(value);
            }
        }
    }

}
